package com.grace.cms.search.lucene.service;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Service;
import org.wltea.analyzer.lucene.IKAnalyzer;
import com.grace.common.enums.GraceServerRoot;
import com.grace.cms.search.exception.SearchServiceException;
import com.grace.cms.search.lucene.document.ArticleDocument;
import com.grace.cms.search.lucene.document.CategoryDocument;
import com.grace.cms.search.lucene.document.SearchableDocument;
import com.grace.cms.search.lucene.document.TagDocument;
import com.grace.cms.search.lucene.domain.DocumentPage;
import jakarta.annotation.PostConstruct;

/**
 * Lucene搜索服务类
 */
@Service
public class LuceneSearchService implements ApplicationRunner {

    private static String INDEX_PATH;
    private Directory directory;
    private Analyzer analyzer;
    private IndexWriter indexWriter;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        INDEX_PATH = GraceServerRoot.LUCENE_DIR.getWorkRoot();
        // 初始化Lucene索引目录
        directory = FSDirectory.open(Paths.get(INDEX_PATH));

    }

    @PostConstruct
    public void init() throws IOException {

        // 使用标准分析器
        analyzer = new IKAnalyzer();
    }

    /**
     * 获取IndexWriter实例
     * 
     * @return IndexWriter
     * @throws IOException
     */
    private IndexWriter getIndexWriter() throws IOException {
        if (indexWriter == null) {
            IndexWriterConfig config = new IndexWriterConfig(analyzer);
            // 设置在创建IndexWriter时解锁
            config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
            indexWriter = new IndexWriter(directory, config);
        }
        return indexWriter;
    }

    /**
     * 添加文档到索引
     * 
     * @param articleDocument 文档
     * @throws IOException
     */
    public void addDocument(ArticleDocument articleDocument) throws IOException {
        IndexWriter writer = getIndexWriter();
        writer.addDocument(articleDocument.toDocument());
        writer.commit();
    }

    /**
     * 添加分类文档到索引
     * 
     * @param categoryDocument 分类文档
     * @throws IOException
     */
    public void addDocument(CategoryDocument categoryDocument) throws IOException {
        IndexWriter writer = getIndexWriter();
        writer.addDocument(categoryDocument.toDocument());
        writer.commit();
    }

    /**
     * 添加标签文档到索引
     * 
     * @param tagDocument 标签文档
     * @throws IOException
     */
    public void addDocument(TagDocument tagDocument) throws IOException {
        IndexWriter writer = getIndexWriter();
        writer.addDocument(tagDocument.toDocument());
        writer.commit();
    }

    /**
     * 批量添加文档到索引
     * 
     * @param documents 文档列表
     * @throws IOException
     */
    public void addDocuments(List<ArticleDocument> documents) throws IOException {
        IndexWriter writer = getIndexWriter();
        for (ArticleDocument document : documents) {
            writer.addDocument(document.toDocument());
        }
        writer.commit();
    }

    /**
     * 根据ID删除文档
     * 
     * @param id 文档ID
     * @throws IOException
     */
    public void deleteDocument(String id) throws IOException {
        IndexWriter writer = getIndexWriter();
        Term term = new Term("id", id);
        writer.deleteDocuments(term);
        writer.commit();
    }

    /**
     * 根据ID和类型删除文档
     * 
     * @param id   文档ID
     * @param type 文档类型
     * @throws IOException
     */
    public void deleteDocument(String id, String type) throws IOException {
        IndexWriter writer = getIndexWriter();
        BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
        booleanQuery.add(new TermQuery(new Term("id", id)), BooleanClause.Occur.MUST);
        booleanQuery.add(new TermQuery(new Term("type", type)), BooleanClause.Occur.MUST);
        writer.deleteDocuments(booleanQuery.build());
        writer.commit();
    }

    /**
     * 更新文章文档
     * 
     * @param articleDocument 文档
     * @throws IOException
     */
    public void updateDocument(ArticleDocument articleDocument) throws IOException {
        IndexWriter writer = getIndexWriter();
        Term term = new Term(ArticleDocument.ID, articleDocument.getId());
        writer.updateDocument(term, articleDocument.toDocument());
        writer.commit();
    }

    /**
     * 更新分类文档
     * 
     * @param categoryDocument 分类文档
     * @throws IOException
     */
    public void updateDocument(CategoryDocument categoryDocument) throws IOException {
        IndexWriter writer = getIndexWriter();
        Term term = new Term(CategoryDocument.ID, categoryDocument.getId());
        writer.updateDocument(term, categoryDocument.toDocument());
        writer.commit();
    }

    /**
     * 更新标签文档
     * 
     * @param tagDocument 标签文档
     * @throws IOException
     */
    public void updateDocument(TagDocument tagDocument) throws IOException {
        IndexWriter writer = getIndexWriter();
        Term term = new Term(TagDocument.ID, tagDocument.getId());
        writer.updateDocument(term, tagDocument.toDocument());
        writer.commit();
    }

    /**
     * 搜索文档
     * 
     * @param queryString 查询字符串
     * @param maxResults  最大结果数
     * @return 搜索结果
     * @throws IOException
     * @throws ParseException
     */
    public List<SearchableDocument> search(String queryString, int maxResults) throws IOException, ParseException {
        try (DirectoryReader reader = DirectoryReader.open(directory)) {
            IndexSearcher searcher = new IndexSearcher(reader);
            // 使用多字段查询
            String[] fields = { ArticleDocument.TITLE, ArticleDocument.CONTENT,
                    CategoryDocument.NAME, CategoryDocument.DESCRIPTION,
                    TagDocument.NAME, TagDocument.DESCRIPTION };
            MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(fields, analyzer);
            Query query = multiFieldQueryParser.parse(queryString);

            TopDocs topDocs = searcher.search(query, maxResults);

            List<SearchableDocument> results = new ArrayList<>();

            // 创建高亮器
            SimpleHTMLFormatter htmlFormatter = new SimpleHTMLFormatter("<span style='color:red;font-weight:bold;'>",
                    "</span>");
            Highlighter highlighter = new Highlighter(htmlFormatter, new QueryScorer(query));

            for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
                Document document = searcher.storedFields().document(scoreDoc.doc);

                // 对文档内容进行高亮处理
                String content = document.get(ArticleDocument.CONTENT);
                String title = document.get(ArticleDocument.TITLE);

                if (content != null) {
                    String highlightedContent;
                    try {
                        highlightedContent = highlighter.getBestFragment(analyzer, ArticleDocument.CONTENT, content);
                        if (highlightedContent != null) {
                            document.removeField(ArticleDocument.CONTENT);
                            document.add(new TextField(ArticleDocument.CONTENT, highlightedContent, Store.YES));
                        }
                    } catch (IOException | InvalidTokenOffsetsException e) {

                        throw new com.grace.cms.search.exception.SearchServiceException("Highlight failed", e);
                    }

                }

                if (title != null) {
                    String highlightedTitle;
                    try {
                        highlightedTitle = highlighter.getBestFragment(analyzer, ArticleDocument.TITLE, title);
                        if (highlightedTitle != null) {
                            document.removeField(ArticleDocument.TITLE);
                            document.add(new TextField(ArticleDocument.TITLE, highlightedTitle, Store.YES));
                        }
                    } catch (IOException | InvalidTokenOffsetsException e) {

                        throw new com.grace.cms.search.exception.SearchServiceException("Highlight failed", e);
                    }

                }

                SearchableDocument searchableDocument = SearchableDocument.fromDocument(document);
                if (searchableDocument != null) {
                    results.add(searchableDocument);
                }
            }
            return results;
        }
    }

    /**
     * 根据类型搜索文档
     * 
     * @param queryString 查询字符串
     * @param type        文档类型
     * @param maxResults  最大结果数
     * @return 搜索结果
     * @throws IOException
     * @throws ParseException
     */
    public List<SearchableDocument> searchByType(String queryString, String type, int maxResults)
            throws IOException, ParseException {
        try (DirectoryReader reader = DirectoryReader.open(directory)) {
            IndexSearcher searcher = new IndexSearcher(reader);
            // 构建布尔查询，同时匹配查询内容和文档类型
            String[] fields = { ArticleDocument.TITLE, ArticleDocument.CONTENT,
                    CategoryDocument.NAME, CategoryDocument.DESCRIPTION,
                    TagDocument.NAME, TagDocument.DESCRIPTION };
            MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(fields, analyzer);
            Query contentQuery = multiFieldQueryParser.parse(queryString);
            Query typeQuery = new TermQuery(new Term("type", type));

            BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
            booleanQuery.add(contentQuery, BooleanClause.Occur.MUST);
            booleanQuery.add(typeQuery, BooleanClause.Occur.MUST);

            TopDocs topDocs = searcher.search(booleanQuery.build(), maxResults);
            List<SearchableDocument> results = new ArrayList<>();

            // 创建高亮器
            SimpleHTMLFormatter htmlFormatter = new SimpleHTMLFormatter("<span style='color:red;font-weight:bold;'>",
                    "</span>");
            Highlighter highlighter = new Highlighter(htmlFormatter, new QueryScorer(contentQuery));

            for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
                Document document = searcher.storedFields().document(scoreDoc.doc);

                // 对文档内容进行高亮处理
                String content = document.get(ArticleDocument.CONTENT);
                String title = document.get(ArticleDocument.TITLE);

                if (content != null) {
                    String highlightedContent;
                    try {
                        highlightedContent = highlighter.getBestFragment(analyzer, ArticleDocument.CONTENT, content);
                        if (highlightedContent != null) {
                            document.removeField(ArticleDocument.CONTENT);
                            document.add(new TextField(ArticleDocument.CONTENT, highlightedContent, Store.YES));
                        }
                    } catch (IOException | InvalidTokenOffsetsException e) {
                        throw new SearchServiceException("Highlight failed", e);
                    }

                }

                if (title != null) {
                    String highlightedTitle;
                    try {
                        highlightedTitle = highlighter.getBestFragment(analyzer, ArticleDocument.TITLE, title);
                        if (highlightedTitle != null) {
                            document.removeField(ArticleDocument.TITLE);
                            document.add(new TextField(ArticleDocument.TITLE, highlightedTitle, Store.YES));
                        }
                    } catch (IOException | InvalidTokenOffsetsException e) {
                        throw new SearchServiceException("Highlight failed", e);
                    }

                }

                SearchableDocument searchableDocument = SearchableDocument.fromDocument(document);
                if (searchableDocument != null) {
                    results.add(searchableDocument);
                }
            }
            return results;
        }
    }

    public DocumentPage<SearchableDocument> search(
            String query,
            int page,
            int size,
            String sortField,
            String sortOrder) throws IOException, ParseException {
        try (DirectoryReader reader = DirectoryReader.open(directory)) {
            IndexSearcher searcher = new IndexSearcher(reader);
            // 构建查询
            String[] fields = { ArticleDocument.TITLE, ArticleDocument.CONTENT,
                    CategoryDocument.NAME, CategoryDocument.DESCRIPTION,
                    TagDocument.NAME, TagDocument.DESCRIPTION };
            MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(fields, analyzer);
            Query luceneQuery = multiFieldQueryParser.parse(query);

            // 查询总数
            TopDocs totalHits = searcher.search(luceneQuery, Integer.MAX_VALUE);
            long totalElements = totalHits.totalHits.value;

            // 构建排序
            Sort sort = buildSort(sortField, sortOrder);

            // 执行分页查询
            TopDocs topDocs = searcher.search(luceneQuery, page * size, sort);
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            int start = (page - 1) * size;
            List<SearchableDocument> content = new ArrayList<>();
            for (int i = start; i < Math.min(start + size, scoreDocs.length); i++) {
                Document doc = searcher.storedFields().document(scoreDocs[i].doc);
                content.add(SearchableDocument.fromDocument(doc));
            }
            return new DocumentPage<>(content, totalElements, page, size);
        }
    }

       /**
     * 根据类型搜索文档（支持分页和排序）
     *
     * @param query     查询字符串
     * @param type      文档类型
     * @param page      页码
     * @param size      每页大小
     * @param sortField 排序字段
     * @param sortOrder 排序顺序
     * @return 搜索结果
     * @throws IOException
     * @throws ParseException
     */
    public DocumentPage<SearchableDocument> searchByType(
            String query,
            String type,
            int page,
            int size,
            String sortField,
            String sortOrder) throws IOException, ParseException {
        try (DirectoryReader reader = DirectoryReader.open(directory)) {
            IndexSearcher searcher = new IndexSearcher(reader);
            // 构建查询
            String[] fields = { ArticleDocument.TITLE, ArticleDocument.CONTENT,
                    CategoryDocument.NAME, CategoryDocument.DESCRIPTION,
                    TagDocument.NAME, TagDocument.DESCRIPTION };
            MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(fields, analyzer);
            Query contentQuery = multiFieldQueryParser.parse(query);
            Query typeQuery = new TermQuery(new Term("type", type));

            BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
            booleanQuery.add(contentQuery, BooleanClause.Occur.MUST);
            booleanQuery.add(typeQuery, BooleanClause.Occur.MUST);

            // 查询总数
            TopDocs totalHits = searcher.search(booleanQuery.build(), Integer.MAX_VALUE);
            long totalElements = totalHits.totalHits.value;

            // 构建排序
            Sort sort = buildSort(sortField, sortOrder);

            // 执行分页查询
            TopDocs topDocs = searcher.search(booleanQuery.build(), page * size, sort);
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            int start = (page - 1) * size;
            List<SearchableDocument> content = new ArrayList<>();
            for (int i = start; i < Math.min(start + size, scoreDocs.length); i++) {
                Document doc = searcher.storedFields().document(scoreDocs[i].doc);
                content.add(SearchableDocument.fromDocument(doc));
            }
            return new DocumentPage<>(content, totalElements, page, size);
        }
    }

    /**
     * 关闭资源
     * 
     * @throws IOException
     */
    public void close() throws IOException {
        if (indexWriter != null) {
            indexWriter.close();
        }
        if (directory != null) {
            directory.close();
        }
    }
    
     private Sort buildSort(String sortField, String sortOrder) {
        SortField.Type type = SortField.Type.STRING; // 默认按字符串排序
        if ("publishDate".equals(sortField)) {
            type = SortField.Type.LONG; // 日期按长整型排序
        } else if ("viewCount".equals(sortField)) {
            type = SortField.Type.LONG; // 浏览量按长整型排序
        }
        SortField sortFieldObj = new SortField(sortField, type, "desc".equalsIgnoreCase(sortOrder));
        return new Sort(sortFieldObj);
    }


}